package com.pug.gateway.filter;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SimplePropertyPreFilter;
import com.pug.gateway.dto.AccessLogReqDTO;
import com.pug.gateway.service.IAccessLogService;
import com.pug.gateway.utils.ServerHttpRequestUtils;
import com.pug.gateway.utils.SignUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.multipart.Part;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.net.URI;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * @author RenGaoshuai
 * @date 2023/5/12 18:28
 * @description 签名过滤器
 */
@Configuration
@Order(2)
@Slf4j
public class SignFilter implements GlobalFilter, Ordered {
    /**
     * 签名验证时间（TIMES =分钟 * 秒 * 毫秒）
     * 当前设置为：5分钟有效期
     */
    protected static final Integer TIMES = 5 * 60 * 1000;

    protected static final String APPTOKEN = "appToken";

    private static final String CLIENTSECRET = "clientSecret";

    private static final String ISSIGN = "isSign";

    protected static final String APPSECRET = "appSecret";

    protected static final String TIMESTAMP = "timestamp";

    protected static final String SIGN = "sign";

    @Value("${ecs.conf.sign.enable}")
    private Boolean enableSign;

    @Autowired
    private IAccessLogService accessLogService;

    // 保存HttpMessageReader
    private static final List<HttpMessageReader<?>> MESSAGE_READERS = HandlerStrategies.withDefaults().messageReaders();


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {


        ServerHttpRequest request = exchange.getRequest();

        log.info("进入签名过滤器--->url:{}", request.getURI());

        if (!enableSign){
            return chain.filter(exchange);
        }

        //url上的请求参数
        Map<String, Object> params = new HashMap<>();
        request.getQueryParams().forEach((key, values) -> {
            params.put(key, values.get(0));
        });

        //post请求需要获取body参数
        if (Objects.equals("GET", request.getMethodValue())) {
            //检查签名
            return checkSign(params, exchange, chain);
        } else if (Objects.equals("POST", request.getMethodValue())) {
            Flux<DataBuffer> body = request.getBody();
            Mono<DataBuffer> join = DataBufferUtils.join(body);
            return join.flatMap(dataBuffer -> {
                DataBufferUtils.retain(dataBuffer);
                final Flux<DataBuffer> cachedFlux = Flux.defer(() -> Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount())));
                final ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
                    @Override
                    public Flux<DataBuffer> getBody() {
                        return cachedFlux;
                    }
                };
                final ServerWebExchange mutatedExchange = exchange.mutate().request(mutatedRequest).build();

                //缓存body，防止本次读取消耗body
                return cacheBody(mutatedExchange, chain, params);
            });
        }

        //放行
        return chain.filter(exchange);
    }

    /**
     * 缓存post请求的body
     * 该过滤器读取body内容后就会被消耗掉，导致后续使用body时为空
     */
    @SuppressWarnings("unchecked")
    private Mono<Void> cacheBody(ServerWebExchange exchange, GatewayFilterChain chain, Map<String, Object> params) {
        final HttpHeaders headers = exchange.getRequest().getHeaders();
        //正文长度，没有body,放行
        if (headers.getContentLength() == 0) {
            return chain.filter(exchange);
        }
        final ResolvableType resolvableType;

        //数据类型 multipart/form-data格式
        if (MediaType.MULTIPART_FORM_DATA.isCompatibleWith(headers.getContentType())) {
            resolvableType = ResolvableType.forClassWithGenerics(MultiValueMap.class, String.class, Part.class);
        } else {
            resolvableType = ResolvableType.forClass(String.class);
        }

        return MESSAGE_READERS.stream().filter(reader -> reader.canRead(resolvableType,
                exchange.getRequest().getHeaders().getContentType())).findFirst()
                .orElseThrow(() -> new IllegalStateException("no suitable HttpMessageReader.")).readMono(resolvableType,
                        exchange.getRequest(), Collections.emptyMap()).flatMap(resolvedBody -> {
                    if (resolvedBody instanceof MultiValueMap) {
                        @SuppressWarnings("rawtypes")
                        MultiValueMap<String, Object> map = (MultiValueMap) resolvedBody;
                        map.keySet().forEach(key -> {
                            Object obj = map.get(key);
                            List<Object> list = (List<Object>) obj;
                            for (Object object : list) {
                                if (object.getClass().toString().equals("class org.springframework.http.codec.multipart.SynchronossPartHttpMessageReader$SynchronossFilePart")) {
                                    continue;
                                }
                                Field[] fields = object.getClass().getDeclaredFields();
                                try {
                                    for (Field field : fields) {
                                        field.setAccessible(true);
                                        params.put(key, field.get(object) + "");
                                    }
                                } catch (IllegalAccessException e) {
                                    e.printStackTrace();
                                }
                            }
                        });
                    } else {
                        if (null != resolvedBody) {
                            try {
                                //为了解决+ %的转义问题
                                String requestBody = resolvedBody.toString();
                                requestBody = requestBody.replaceAll("%(?![0-9a-fA-F]{2})", "%25");
                                requestBody = requestBody.replaceAll("\\+", "%2B");
                                JSONObject jsonObject = JSONObject.parseObject(URLDecoder.decode(requestBody, "UTF-8"));
                                params.putAll(jsonObject);
                            } catch (UnsupportedEncodingException e) {
                                e.printStackTrace();
                            }
                        }

                    }

                    //检查签名
                    return this.checkSign(params, exchange, chain);

                });
    }

    private Mono<Void> checkSign(Map<String, Object> params, ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        HttpHeaders headers = request.getHeaders();

        // TODO 记录日志
        log.info("doSaveLog成功-------------------");

        this.doSaveLog(params,exchange);

        String isSign = headers.getFirst(ISSIGN);


        if (Objects.equals(isSign, "0")) {
            //放行
            log.info("isSign=0,忽略签名，直接放行");
            return chain.filter(exchange);
        }

        //验签
        String timestamp = headers.getFirst(TIMESTAMP);
        if (StrUtil.isNotBlank(timestamp)) {
            params.put(TIMESTAMP, timestamp);
        }
        String clientSecret = headers.getFirst(CLIENTSECRET);
        params.put(CLIENTSECRET, clientSecret);

        //TODO 生成签名
        String sign = headers.getFirst(SIGN);
        params.put(SIGN, sign);

        Map<String, Object> errorValidationMap = SignUtil.validation(params, SIGN, TIMESTAMP, TIMES, APPSECRET);

        if (CollectionUtils.isEmpty(errorValidationMap)) {
            // 放行
            return chain.filter(exchange);
        }

        //验签不通过
        return invalidSign(exchange, errorValidationMap);

    }

    /**
     * 保存网关请求日志
     * 该方法即使出错也不遑 阻塞流程，内部会捕获异常
     * @param params 参数
     * @param exchange
     */
    private void doSaveLog(Map<String, Object> params, ServerWebExchange exchange) {
        try {
            AccessLogReqDTO dto = new AccessLogReqDTO();
            ServerHttpRequest request = exchange.getRequest();

            //请求方式
            String method = request.getMethodValue();
            dto.setMethod(method);

            //请求uri
            URI uri = request.getURI();
            dto.setUrl(uri.toString());

            //请求头部
            HttpHeaders headers = request.getHeaders();
            dto.setHeaders(headers.toString());

            //头部appToken字段（能进到这里appToken肯定不为空）
            String appToken = headers.getFirst(APPTOKEN);
            dto.setClientId(appToken);


            //请求ip
            String ip = ServerHttpRequestUtils.getIpAddress(request);
            dto.setClientIp(ip);

            //请求参数(排除multipartFile字段)
            if (uri.toString().contains("/getSignFileList")){
                SimplePropertyPreFilter excludeFilter = new SimplePropertyPreFilter();
                excludeFilter.getExcludes().add("multipartFile");
                dto.setParamsData(JSON.toJSONString(params,excludeFilter));
            }else{
                dto.setParamsData(JSON.toJSONString(params));
            }

            //入库（该方法不会阻塞流程）
            accessLogService.saveLogger(dto);
        } catch (Exception e) {
            log.error("保存请求日志失败，原因：" + e);
        }
    }



    /**
     * 网关拒绝，返回400 无效签名
     *
     * @param
     */
    private Mono<Void> invalidSign(ServerWebExchange serverWebExchange, Map<String, Object> validResult) {
        serverWebExchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);
        DataBuffer buffer = serverWebExchange.getResponse()
                .bufferFactory().wrap(JSON.toJSONString(validResult).getBytes(StandardCharsets.UTF_8));
        return serverWebExchange.getResponse().writeWith(Flux.just(buffer));
    }

    @Override
    public int getOrder() {
        return 2;
    }
}
